---
title: Publishing a package
layout: learn
authors: JakobJingleheimer
---

# Publishing a package

All the provided `package.json` configurations (not specifically marked “does not work”) work in Node.js 12.22.x (v12 latest, the oldest supported line) and 17.2.0 current latest at the time[^1], and for grins, with webpack 5.53.0 and 5.63.0 respectively. These are available: [JakobJingleheimer/nodejs-module-config-examples](https://github.com/JakobJingleheimer/nodejs-module-config-examples).

For curious cats, [How did we get here](#how-did-we-get-here) and [Down the rabbit-hole](#down-the-rabbit-hole) provide background and deeper explanations.

## Pick your fix

There are 2 main options, which cover almost all use-cases:

- Write source code and publish in CJS (you use `require()`); CJS is consumable by both CJS and ESM (in all versions of node). Skip to [CJS source and distribution](#cjs-source-and-distribution).
- Write source code and publish in ESM (you use `import`, and don't use top-level `await`); ESM is consumable by both ESM and CJS (in node 22.x and 23.x; see [`require()` an ES Module](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require)). Skip to [ESM source and distribution](#esm-source-and-distribution).

It's generally best to publish only 1 format, either CJS _or_ ESM. Not both. Publishing multiple formats can result in the [dual-package hazard](#the-dual-package-hazard), as well as other drawbacks.

There are other options available, mostly for historical purposes.

<table>
  <thead>
    <tr>
      <th>You as a package author write</th>
      <th>Consumers of your package write their code in</th>
      <th>Your options</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td rowSpan="2">CJS source code using <code>require()</code></td>
      <td>ESM: consumers <code>import</code> your package</td>
      <td><a href="#cjs-source-and-only-esm-distribution">CJS source and only ESM distribution</a></td>
    </tr>

    <tr>
      <td>CJS & ESM: consumers either <code>require()</code> or <code>import</code> your package</td>
      <td><a href="#cjs-source-and-both-cjs-amp-esm-distribution">CJS source and both CJS & ESM distribution</a></td>
    </tr>

    <tr>
      <td rowSpan="2">ESM source code using <code>import</code></td>
      <td>CJS: consumers <code>require()</code> your package (and you use top-level <code>await</code>)</td>
      <td><a href="#esm-source-with-only-cjs-distribution">ESM source with only CJS distribution</a></td>
    </tr>

    <tr>
      <td>CJS & ESM: consumers either <code>require()</code> or <code>import</code> your package</td>
      <td><a href="#esm-source-and-both-cjs-amp-esm-distribution">ESM source and both CJS & ESM distribution</a></td>
    </tr>

  </tbody>
</table>

### CJS source and distribution

The most minimal configuration may be only [`"name"`](https://nodejs.org/api/packages.html#name). But the less arcane, the better: Essentially just declare the package’s exports via the `"exports"` field/field-set.

**Working example**: [cjs-with-cjs-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/cjs-distro)

```json displayName="Minimal package.json"
{
  "name": "cjs-source-and-distribution"
  // "main": "./index.js"
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-source-and-distribution",
  "type": "commonjs", // current default, but may change
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": "./dist/index.js",
    "./package.json": "./package.json"
  }
}
```

Note that `packageJson.exports["."] = filepath` is shorthand for `packageJson.exports["."].default = filepath`

### ESM source and distribution

Simple, tried, and true.

Note that since Node.js v23.0.0, it is possible to `require` static ESM (code that does not use top-level `await`). See [Loading ECMAScript modules using `require()`](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) for details.

This is almost exactly the same as the CJS-CJS configuration above with 1 small difference: the [`"type"`](https://nodejs.org/api/packages.html#type) field.

**Working example**: [esm-with-esm-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/esm-distro)

```json displayName="Minimal package.json"
{
  "name": "esm-source-and-distribution",
  "type": "module"
  // "main": "./index.js"
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "esm-source-and-distribution",
  "type": "module",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": "./dist/index.js",
    "./package.json": "./package.json"
  }
}
```

Note that ESM now _is_ “backwards” compatible with CJS: a CJS module now _can_ [`require()` an ES Module](https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require) without a flag as of 23.0.0 and 22.12.0.

### CJS source and only ESM distribution

This takes a small bit of finesse but is also pretty straight-forward. This may be the choice pick of older projects targetting newer standards, or authors who merely prefer CJS but are publishing for a different environment.

**Working example**: [cjs-with-esm-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/esm-distro)

```json displayName="Minimal package.json"
{
  "name": "cjs-source-with-esm-distribution",
  "main": "./dist/index.mjs"
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-source-with-esm-distribution",
  "type": "commonjs", // current default, but may change
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": "./dist/index.mjs",
    "./package.json": "./package.json"
  }
}
```

The [`.mjs`](https://nodejs.org/api/esm.html#enabling) file extension is a trump-card: it will override **any** other configuration and the file will be treated as ESM. Using this file extension is necessary because `packageJson.exports.import` does **NOT** signify that the file is ESM (contrary to common, if not universal, misperception), only that it is the file to be used when the package is imported (ESM _can_ import CJS. See [Gotchas](#gotchas) below).

### CJS source and both CJS & ESM distribution

In order to _directly_ supply both audiences (so that your distribution works "natively" in either), you have a few options:

#### Attach named exports directly onto `exports`

Classic but takes some sophistication and finesse. This means adding properties onto the existing `module.exports` (instead of re-assigning `module.exports` as a whole).

**Working example**: [cjs-with-dual-distro (properties)](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/property-distro)

```json displayName="Minimal package.json"
{
  "name": "cjs-source-with-esm-via-properties-distribution",
  "main": "./dist/cjs/index.js"
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-source-with-esm-via-properties-distribution",
  "type": "commonjs", // current default, but may change
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": "./dist/cjs/index.js",
    "./package.json": "./package.json"
  }
}
```

Pros:

- Smaller package weight
- Easy and simple (probably least effort if you don't mind keeping to a minor syntax stipulation)
- Precludes [the Dual-Package Hazard](#the-dual-package-hazard)

Cons:

- Requires very specific syntax (either in source code and/or bundler gymnastics).

Sometimes, a CJS module may re-assign `module.exports` to something else (be it an object or a function) like this:

```cjs
const someObject = {
  foo() {},
  bar() {},
  qux() {},
};

module.exports = someObject;
```

Node.js detects the named exports in CJS via [static analysis that look for certain patterns](https://github.com/nodejs/cjs-module-lexer/tree/main?tab=readme-ov-file#parsing-examples), which the example above evades. To make the named exports detectable, do this:

```cjs
module.exports.foo = function foo() {};
module.exports.bar = function bar() {};
module.exports.qux = function qux() {};
```

#### Use a simple ESM wrapper

Complicated setup and difficult to get the balance right.

**Working example**: [cjs-with-dual-distro (wrapper)](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/wrapper-distro)

```json displayName="Minimal package.json"
{
  "name": "cjs-with-wrapper-dual-distro",
  "exports": {
    ".": {
      "import": "./dist/esm/wrapper.mjs",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    }
  }
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-with-wrapper-dual-distro",
  "type": "commonjs",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "import": "./dist/esm/wrapper.mjs",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    },
    "./package.json": "./package.json"
  }
}
```

Pros:

- Smaller package weight

Cons:

- Likely requires complicated bundler gymnastics (we could not find any existing option to automate this in Webpack).

When the CJS output from the bundler evades the named exports detection in Node.js, a ESM wrapper can be used to explicitly re-export the known named exports for ESM consumers.

When CJS exports an object (which gets aliased to ESM's `default`), you can save references to all the members of the object locally in the wrapper, and then re-export them so the ESM consumer can access all of them by name.

```js displayName="./dist/esm/wrapper.mjs"
import cjs from '../cjs/index.js';

const { a, b, c /* … */ } = cjs;

export { a, b, c /* … */ };
```

**However**, this does break live bindings: a reassignment to `cjs.a` will not reflect in `esmWrapper.a`.

#### Two full distributions

Chuck in a bunch of stuff and hope for the best. This is probably the most common and easiest of the CJS to CJS & ESM options, but you pay for it. This is rarely a good idea.

**Working example**: [cjs-with-dual-distro (double)](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/cjs/dual/double-distro)

```json displayName="Minimal package.json"
{
  "name": "cjs-with-full-dual-distro",
  "exports": {
    ".": {
      "import": "./dist/esm/index.mjs",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    }
  }
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-with-full-dual-distro",
  "type": "commonjs",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "import": "./dist/esm/index.mjs",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    },
    "./package.json": "./package.json"
  }
}
```

Pros:

- Simple bundler configuration

Cons:

- Larger package weight (basically double)
- Vulnerable to [the Dual-Package Hazard](#the-dual-package-hazard)

Alternatively, you can use `"default"` and `"node"` keys, which are less counter-intuitive: Node.js will always choose the `"node"` option (which always works), and non-Node.js tooling will choose `"default"` when configured to target something other than node. **This precludes the dual-package hazard.**

```json displayName="Minimal package.json"
{
  "name": "cjs-with-alt-full-dual-distro",
  "exports": {
    ".": {
      "node": "./dist/cjs/index.js",
      "default": "./dist/esm/index.mjs"
    }
  }
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "cjs-with-alt-full-dual-distro",
  "type": "commonjs",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "node": "./dist/cjs/index.js",
      "default": "./dist/esm/index.mjs"
    },
    "./package.json": "./package.json"
  }
}
```

### ESM source with only CJS distribution

We're not in Kansas anymore, Toto.

The configurations (there are 2 options) are nearly the same as [ESM source and both CJS & ESM distribution](#esm-source-and-both-cjs-amp-esm-distribution), just exclude `packageJson.exports.import`.

💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below.

**Working example**: [esm-with-cjs-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/cjs-distro)

### ESM source and both CJS & ESM distribution

When source code is written in non-JavaScript (ex TypeScript), options can be limited due to needing to use file extension(s) specific to that language (ex `.ts`) and there may be no `.mjs` equivalent.

Similar to [CJS source and both CJS & ESM distribution](#cjs-source-and-both-cjs-amp-esm-distribution), you have the same options.

#### Publish only a CJS distribution with property exports

Tricky to make and needs good ingredients.

This option is almost identical to the [CJS source with CJS & ESM distribution's property exports](#attach-named-exports-directly-onto-raw-exports-endraw-) above. The only difference is in package.json: `"type": "module"`.

Only some build tools support generating this output. [Rollup](https://www.rollupjs.org/) produces compatible output out of the box when targetting commonjs. Webpack as of [v5.66.0+](https://github.com/webpack/webpack/releases/tag/v5.66.0) does with the new [`commonjs-static`](https://webpack.js.org/configuration/output/#type-commonjs-static) output type, (prior to this, no commonjs options produces compatible output). It is not currently possible with [esbuild](https://esbuild.github.io/) (which produces a non-static `exports`).

The working example below was created prior to Webpack's recent release, so it uses Rollup (I'll get around to adding a Webpack option too).

These examples assume javascript files within use the extension `.js`; `"type"` in `package.json` controls how those are interpreted:

`"type":"commonjs"` + `.js` → `cjs`<br />
`"type":"module"` + `.js` → `mjs`

If your files explicitly _all_ use `.cjs` and/or `.mjs` file extensions (none use `.js`), `"type"` is superfluous.

**Working example**: [esm-with-cjs-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/property-distro)

```json displayName="Minimal package.json"
{
  "name": "esm-with-cjs-distribution",
  "type": "module",
  "main": "./dist/index.cjs"
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "esm-with-cjs-distribution",
  "type": "module",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": "./dist/index.cjs",
    "./package.json": "./package.json"
  }
}
```

💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below.

#### Publish a CJS distribution with an ESM wrapper

There's a lot going on here, and this is usually not the best.

This is also almost identical to the [CJS source and dual distribution using an ESM wrapper](#use-a-simple-esm-wrapper), but with subtle differences `"type": "module"` and some `.cjs` file extenions in package.json.

**Working example**: [esm-with-dual-distro (wrapper)](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/wrapper-distro)

```json displayName="Minimal package.json"
{
  "name": "esm-with-cjs-and-esm-wrapper-distribution",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/esm/wrapper.js",
      "require": "./dist/cjs/index.cjs",
      "default": "./dist/cjs/index.cjs"
    }
  }
}
```

```json displayName="Advanced (verbose) package.json"
{
  "name": "esm-with-cjs-and-esm-wrapper-distribution",
  "type": "module",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "import": "./dist/esm/wrapper.js",
      "require": "./dist/cjs/index.cjs",
      "default": "./dist/cjs/index.cjs"
    },
    "./package.json": "./package.json"
  }
}
```

💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below.

#### Publish both full CJS & ESM distributions

Chuck in a bunch of stuff (with a surprise) and hope for the best. This is probably the most common and easiest of the ESM to CJS & ESM options, but you pay for it. This is rarely a good idea.

In terms of package configuration, there are a few options that differ mostly in personal preference.

##### Mark the whole package as ESM and specifically mark the CJS exports as CJS via the `.cjs` file extension

This option has the least burden on development/developer experience.

This also means that whatever build tooling must produce the distribution file with a `.cjs` file extension. This might necessitate chaining multiple build tools or adding a subsequent step to move/rename the file to have the `.cjs` file extension (ex `mv ./dist/index.js ./dist/index.cjs`). This can be worked around by adding a subsequent step to move/rename those outputted files (ex [Rollup](https://rollupjs.org/) or [a simple shell script](https://stackoverflow.com/q/21985492)).

Support for the `.cjs` file extension was added in 12.0.0, and using it will cause ESM to properly recognised a file as commonjs (`import { foo } from './foo.cjs'` works). However, `require()` does not auto-resolve `.cjs` like it does for `.js`, so file extension cannot be omitted as is commonplace in commonjs: `require('./foo')` will fail, but `require('./foo.cjs')` works. Using it in your package's exports has no drawbacks: `packageJson.exports` (and `packageJson.main`) requires a file extension regardless, and consumers reference your package by the `"name"` field of your package.json (so they're blissfully unaware).

**Working example**: [esm-with-dual-distro](https://github.com/JakobJingleheimer/nodejs-module-config-examples/tree/main/packages/esm/dual/double-distro)

```json displayName="Minimal import & require package.json"
{
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/index.cjs"
    }
  }
}
```

```json displayName="Advanced (verbose) import & require package.json"
{
  "type": "module",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/index.cjs"
    },
    "./package.json": "./package.json"
  }
}
```

Alternatively, you can use `"default"` and `"node"` keys, which are less counter-intuitive: Node.js will always choose the `"node"` option (which always works), and non-Node.js tooling will choose `"default"` when configured to target something other than node. **This precludes the dual-package hazard.**

```json displayName="Minimal default & node package.json"
{
  "type": "module",
  "exports": {
    ".": {
      "node": "./dist/index.cjs",
      "default": "./dist/esm/index.js"
    }
  }
}
```

```json displayName="Advanced (verbose) default & node package.json"
{
  "type": "module",
  "engines": { "node": ">=12.22.7" },
  "exports": {
    ".": {
      "node": "./dist/index.cjs",
      "default": "./dist/esm/index.js"
    },
    "./package.json": "./package.json"
  }
}
```

💡 Using `"type": "module"`[^2] paired with the `.cjs` file extension (for commonjs files) yields best results. For more information on why, see [Down the rabbit-hole](#down-the-rabbit-hole) and [Gotchas](#gotchas) below.

##### Use the `.mjs` (or equivalent) file extension for all source code files

The configuration for this is the same as [CJS source and both CJS & ESM distribution](#cjs-source-and-both-cjs-amp-esm-distribution).

**Non-JavaScript source code**: The non-JavaScript language’s own configuration needs to recognise/specify that the input files are ESM.

#### Node.js before 12.22.x

🛑 You should not do this: Versions of Node.js prior to 12.x are End of Life and are now vulnerable to serious security exploits.

If you're a security researcher needing to investigate Node.js prior to v12.22.x, feel free to contact us for help configuring.

## General notes

[Syntax detection](https://nodejs.org/api/packages.html#syntax-detection) is _**not**_ a replacement for proper package configuration; syntax detection is not fool-proof and it has [significant performance cost](https://github.com/nodejs/node/pull/55238).

When using [`"exports"`](https://nodejs.org/api/packages.html#conditional-exports) in package.json, it is generally a good idea to include `"./package.json": "./package.json"` so that it can be imported ([`module.findPackageJSON`](https://nodejs.org/api/module.html#modulefindpackagejsonspecifier-base) is not affected by this limitation, but `import` may be more convenient).

`"exports"` can be advisable over [`"main"`](https://nodejs.org/api/packages.html#main) because it prevents external access to internal code (so you can be relatively sure users are not depending on things they shouldn't). If you don't need that, `"main"` is simpler and may be a better option for you.

The `"engines"` field provides both a human-friendly and a machine-friendly indication of which version(s) of Node.js the package is compatible. Depending on the package manager used, an exception may be thrown causing the installation to fail when the consumer is using an incompatible version of Node.js (which can be very helpful to consumers). Including this field will save a lot of headache for consumers with an older version of Node.js who cannot use the package.

## Down the rabbit-hole

Specifically in relation to Node.js, there are 4 problems to solve:

- Determining format of source code files (author running their own code)

- Determining format of distribution files (code consumers will receive)

- Publicising distribution code for when it is `require()`’d (consumer expects CJS)

- Publicising distribution code for when it is `import`’d (consumer probably wants ESM)

⚠️ The first 2 are **independent** of the last 2.

The method of loading does NOT determine the format the file is interpreted as:

- **package.json’s** **`exports.require`** **≠** **`CJS`**. `require()` does NOT and cannot blindly interpret the file as CJS; for instance, `require('foo.json')` correctly interprets the file as JSON, not CJS. The module containing the `require()` call of course must be CJS, but what it is loading is not necessarily also CJS.
- **package.json’s** **`exports.import`** **≠** **`ESM`**. `import` similarly does NOT and cannot blindly interpret the file as ESM; `import` can load CJS, JSON, and WASM, as well as ESM. The module containing the `import` statement of course must be ESM, but what it is loading is not necessarily also ESM.

So when you see configuration options citing or named with `require` or `import`, resist the urge to assume they are for _determining_ CJS vs ES Modules.

⚠️ Adding an `"exports"` field/field-set to a package’s configuration effectively [blocks deep pathing into the package](https://nodejs.org/api/packages.html#package-entry-points) for anything not explicitly listed in the exports’ subpathing. This means it can be a breaking change.

⚠️ Consider carefully whether to distribute both CJS and ESM: It creates the potential for the [Dual Package Hazard](#the-dual-package-hazard) (especially if misconfigured and the consumer tries to get clever). This can lead to an extremely confusing bug in consuming projects, especially when your package is not perfectly configured. Consumers can even be blind-sided by an intermediary package that uses the "other" format of your package (e.g. consumer uses the ESM distribution, and some other package the consumer is also using itself uses the CJS distribution). If your package is in any way stateful, consuming both the CJS and ESM distributions will result in parallel states (which is almost surely unintentional).

### The dual-package hazard

When an application is using a package that provides both CommonJS and ES module sources, there is a risk of certain bugs if both instances of the package get loaded. This potential comes from the fact that the `pkgInstance` created by `const pkgInstance = require('pkg')` is not the same as the `pkgInstance` created by `import pkgInstance from 'pkg'` (or an alternative main path like `'pkg/module'`). This is the “dual package hazard”, where two instances of the same package can be loaded within the same runtime environment. While it is unlikely that an application or package would intentionally load both instances directly, it is common for an application to load one copy while a dependency of the application loads the other copy. This hazard can happen because Node.js supports intermixing CommonJS and ES modules, and can lead to unexpected and confusing behavior.

If the package main export is a constructor, an `instanceof` comparison of instances created by the two copies returns `false`, and if the export is an object, properties added to one (like `pkgInstance.foo = 3`) are not present on the other. This differs from how `import` and `require` statements work in all-CommonJS or all-ES module environments, respectively, and therefore is surprising to users. It also differs from the behavior users are familiar with when using transpilation via tools like [Babel](https://babeljs.io/) or [`esm`](https://github.com/standard-things/esm#readme).

### How did we get here

[CommonJS (CJS)](https://wiki.commonjs.org/wiki/Modules) was created _long_ before ECMAScript Modules (ESM), back when JavaScript was still adolescent—CJS and jQuery were created just 3 years apart. CJS is not an official ([TC39](https://tc39.es)) standard and is supported by a limited few platforms (most notably, Node.js). ESM as a standard has been incoming for several years; it is currently supported by all major platforms (browsers, Deno, Node.js, etc), meaning it will run pretty much everywhere. As it became clear ESM would effectively succeed CJS (which is still very popular and widespread), many attempted to adopt early on, often before a particular aspect of the ESM specification was finalised. Because of this, those changed over time as better information became available (often informed by learnings/experiences of those eager beavers), going from best-guess to aligning with the specification.

An additional complication is bundlers, which historically managed much of this territory. However, much of what we previously needed bundle(r)s to manage is now native functionality; yet bundlers are still (and likely always will be) necessary for some things. Unfortunately, functionality bundlers no-longer need to provide is deeply ingrained in older bundlers’ implementations, so they can at times be too helpful, and in some cases, anti-pattern (bundling a library is often not recommended by bundler authors themselves). The hows and whys of that are an article unto itself.

## Gotchas

The `package.json`'s `"type"` field changes the `.js` file extension to mean either `commonjs` or ES `module` respectively. It is very common in dual/mixed packages (that contain both CJS and ESM) to use this field incorrectly.

```json displayName="⚠️ THIS DOES NOT WORK"
{
  "type": "module",
  "main": "./dist/CJS/index.js",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    },
    "./package.json": "./package.json"
  }
}
```

This does not work because `"type": "module"` causes `packageJson.main`, `packageJson.exports["."].require`, and `packageJson.exports["."].default` to get interpreted as ESM (but they’re actually CJS).

Excluding `"type": "module"` produces the opposite problem:

```json displayName="⚠️ THIS DOES NOT WORK"
{
  "main": "./dist/CJS/index.js",
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "default": "./dist/cjs/index.js"
    },
    "./package.json": "./package.json"
  }
}
```

This does not work because `packageJson.exports["."].import` will get interpreted as CJS (but it’s actually ESM).

[^1]: There was a bug in Node.js v13.0–13.6 where `packageJson.exports["."]` had to be an array with verbose config options as the first item (as an object) and the “default” as the second item (as a string). See [nodejs/modules#446](https://github.com/nodejs/modules/issues/446).

[^2]: The `"type"` field in package.json changes what the `.js` file extension means, similar to to an [HTML script element’s type attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type).
